﻿// <copyright file="EslTokenReader.cs" company="Codeplex project Eco.Contrib"> 
// The MIT License (MIT) 
// </copyright> 
// <author>Peter Buchmann</author> 

namespace Eco.Contrib
{
    using System.Linq;

    /// <summary>
    /// ESLTokenReader: Token reader for ESL
    /// </summary>
    internal class EslTokenReader
    {
        /// <summary>
        /// CBeginningOfWord constant
        /// </summary>
        private const string CBeginningOfWord = "abcdefghijklmnopqrstuvwxyzäüöABCDEFGHIJKLMNOPQRSTUVWXYZÄÜÖß_";

        /// <summary>
        /// CLetter constant
        /// </summary>
        private const string CLetter = "abcdefghijklmnopqrstuvwxyzäüöABCDEFGHIJKLMNOPQRSTUVWXYZÄÜÖß1234567890_";

        /// <summary>
        /// CBeginningOfNumber constant
        /// </summary>
        private const string CBeginningOfNumber = "1234567890";

        /// <summary>
        /// CNumber constant
        /// </summary>
        private const string CNumber = "1234567890.,Ee+-";

        /// <summary>
        /// COperator constant
        /// </summary>
        private const string COperator = ":.,;<>=/*+-^%?!()[]{}$";

        /// <summary>
        /// COperatorWithMoreThanOneChar constant
        /// </summary>
        private const string COperatorWithMoreThanOneChar = ":/><";

        /// <summary>
        /// CStringConstantDelimiter constant
        /// </summary>
        private const string CStringConstantDelimiter = "\"'";

        /// <summary>
        /// Initializes a new instance of the <see cref="EslTokenReader"/> class.
        /// </summary>
        /// <param name="program">The program.</param>
        public EslTokenReader(string program)
        {
            this.Parser = new StringParser(program);
        }

        /// <summary>
        /// Gets or sets the parser.
        /// </summary>
        /// <value>
        /// The parser.
        /// </value>
        private StringParser Parser { get; set; }

        /// <summary>
        /// Gets the next token.
        /// </summary>
        /// <returns>Next token</returns>
        public EslToken GetNextToken()
        {
            CharWithPosition cwpChar = this.Parser.ReadNextChar(true);
            EslTokenType tteTokenType = EslTokenType.Unknown;
            EslToken returnValue;
            string tokenValue = string.Empty;

            if (cwpChar.Char == '\0')
            {
                tteTokenType = EslTokenType.End;
                returnValue = new EslToken(tteTokenType, string.Empty, cwpChar.Line, cwpChar.Col, cwpChar.Pos);
            }
            else
            {
                if (CBeginningOfWord.Contains(cwpChar.Char))
                {
                    tteTokenType = EslTokenType.Word;
                    tokenValue = cwpChar.Char.ToString() + ReadToEndOfWord(this.Parser);
                }
                else
                    if (CBeginningOfNumber.Contains(cwpChar.Char))
                    {
                        tteTokenType = EslTokenType.Number;
                        tokenValue = cwpChar.Char.ToString() + ReadToEndOfNumber(this.Parser);
                    }
                    else
                        if (COperator.Contains(cwpChar.Char))
                        {
                            tteTokenType = EslTokenType.Operator;
                            tokenValue = cwpChar.Char.ToString();
                            if (COperatorWithMoreThanOneChar.Contains(cwpChar.Char))
                            {
                                tokenValue += ReadToEndOfOperator(this.Parser);
                            }

                            if (tokenValue == "//")
                            {
                                tteTokenType = EslTokenType.Comment;
                                tokenValue += ReadToEndOfLine(this.Parser);
                            }
                        }
                        else
                            if (CStringConstantDelimiter.Contains(cwpChar.Char))
                            {
                                tteTokenType = EslTokenType.StringContent;
                                tokenValue = cwpChar.Char.ToString() + ReadToEndOfStringConstant(this.Parser, cwpChar.Char);
                            }

                returnValue = new EslToken(tteTokenType, tokenValue, cwpChar.Line, cwpChar.Col, cwpChar.Pos);
            }

            return returnValue;
        }

        /// <summary>
        /// Reads to end of word.
        /// </summary>
        /// <param name="parser">The parser.</param>
        /// <returns>string up to the end of the current word</returns>
        private static string ReadToEndOfWord(StringParser parser)
        {
            string stringValue = string.Empty;
            CharWithPosition cwpChar = parser.ReadNextChar(false);
            while (CLetter.Contains(cwpChar.Char))
            {
                stringValue += cwpChar.Char.ToString();
                cwpChar = parser.ReadNextChar(false);
            }

            parser.ReverseLastChar();
            return stringValue;
        }

        /// <summary>
        /// Reads to end of number.
        /// </summary>
        /// <param name="parser">The parser.</param>
        /// <returns>string up to the end of the current number</returns>
        private static string ReadToEndOfNumber(StringParser parser)
        {
            string stringValue = string.Empty;
            CharWithPosition cwpChar = parser.ReadNextChar(false);
            while (CNumber.Contains(cwpChar.Char))
            {
                stringValue += cwpChar.Char.ToString();
                cwpChar = parser.ReadNextChar(false);
            }

            parser.ReverseLastChar();
            return stringValue;
        }

        /// <summary>
        /// Reads to end of operator.
        /// </summary>
        /// <param name="parser">The parser.</param>
        /// <returns>string containing all chars of operator</returns>
        private static string ReadToEndOfOperator(StringParser parser)
        {
            string stringValue = string.Empty;
            CharWithPosition cwpChar = parser.ReadNextChar(false);
            while (COperator.Contains(cwpChar.Char))
            {
                stringValue += cwpChar.Char.ToString();
                cwpChar = parser.ReadNextChar(false);
            }

            parser.ReverseLastChar();
            return stringValue;
        }

        /// <summary>
        /// Reads to end of string constant.
        /// </summary>
        /// <param name="parser">The parser.</param>
        /// <param name="endDelimiter">The end delimiter.</param>
        /// <returns>string containing complete string constant</returns>
        private static string ReadToEndOfStringConstant(StringParser parser, char endDelimiter)
        {
            string stringValue = string.Empty;
            CharWithPosition cwpChar;
            do
            {
                cwpChar = parser.ReadNextChar(false);
                stringValue += cwpChar.Char.ToString();
            }
            while ((cwpChar.Char != '\0') && (cwpChar.Char != endDelimiter));
            return stringValue;
        }

        /// <summary>
        /// Reads to end of line.
        /// </summary>
        /// <param name="parser">The parser.</param>
        /// <returns>string containing the end of the line</returns>
        private static string ReadToEndOfLine(StringParser parser)
        {
            string stringValue = string.Empty;
            CharWithPosition cwpChar;
            do
            {
                cwpChar = parser.ReadNextChar(false);
                stringValue += cwpChar.Char.ToString();
            }
            while ((cwpChar.Char != '\0') && (cwpChar.Char != '\n'));
            stringValue = stringValue.Remove(stringValue.Length - 1);
            return stringValue;
        }
    }
}
